//
//  RunloopViewController.h
//  ReviewDemo
//
//  Created by 龙 on 2018/3/26.
//  Copyright © 2018年 long. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface RunloopViewController : UIViewController

@end

/*
 https://opensource.apple.com/tarballs/CF/  RunLoop源码
 
 https://blog.ibireme.com/2015/05/18/runloop/ RunLoop讲解
 深入理解RunLoop

 一、RunLoop 的概念
 
 CFRunLoopRef提供了纯C语言的api，所以是线程安全。
 而NSRunLoop是基于CFRunLoopRef的封装，符合了面向对象的特点，但是不是线程安全的。
 
 二、RunLoop 与 线程的关系
 1、观看 oc 源代码
 
 static CFMutableDictionaryRef __CFRunLoops = NULL;//声明全局字典
 static CFLock_t loopsLock = CFLockInit;
 
 // should only be called by Foundation
 // t==0 is a synonym for "main thread" that always works
 CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
         if (pthread_equal(t, kNilPthreadT)) {
         t = pthread_main_thread_np();
     }
     __CFLock(&loopsLock);
     if (!__CFRunLoops) {  // 第一次进入时，初始化全局Dic，并先为主线程创建一个 RunLoop。
         __CFUnlock(&loopsLock);
         CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
         CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
         CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);//key 为 线程 ；value 为 runloop
         if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
         CFRelease(dict);
         }
         CFRelease(mainLoop);
         __CFLock(&loopsLock);
     }
 
     // 根据 线程 pthread为 key 取出对应的 value --> CFRunLoopRef
     CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
     __CFUnlock(&loopsLock);
     if (!loop) {
         CFRunLoopRef newLoop = __CFRunLoopCreate(t);
         __CFLock(&loopsLock);
         loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
         if (!loop) {
         CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
         loop = newLoop;
         }
         // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
         __CFUnlock(&loopsLock);
         CFRelease(newLoop);
     }
     if (pthread_equal(t, pthread_self())) {
         _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
         if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
         _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
         }
     }
     return loop;
 }

 2、上述代码中定义了两个重要的函数CFRunLoopGetMain()和CFRunLoopGetCurrent()；
    在初始化获取的时候,首先会去全局字典中获取,当没有的时候就会重新根据对应的线程生成一个runLoop,
    它的key及为当前对应的线程,所以从这里可以看出,每个线程与它的RunLoop是一一对应的。
    同时也注册了一个线程销毁时的回调block _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);。
 3、总结：
 （1）、主线程在程序运行的时候会创建唯一的一个MainRunLoop(根runLoop,实际测试还可以嵌套,因为它时while循环为前提的,在一个runLoop上可以嵌套其他的runLoop),在这里称最上层的RunLoop为根runLoop.
 （2）、线程与RunLoop之间是一一对应的,子线程需要通过CFGetCurrentRunLoop才能获取对应的runLoop。
 （3）、线程销毁的时候，和它绑定的runLoop也会一起销毁。
     线程和 RunLoop 之间是一一对应的，其关系是保存在一个全局的 Dictionary 里。
     线程刚创建时并没有 RunLoop，如果你不主动获取，那它一直都不会有。
     RunLoop 的创建是发生在第一次获取时，RunLoop 的销毁是发生在线程结束时。
     你只能在一个线程的内部获取其 RunLoop（主线程除外）。
 
 三、RunLoop 对外的接口
 1、在 CoreFoundation 里面关于 RunLoop 有5个类:
     CFRunLoopRef
     CFRunLoopModeRef
     CFRunLoopSourceRef
     CFRunLoopTimerRef
     CFRunLoopObserverRef
     一个 RunLoop 包含若干个 Mode，每个 Mode 又包含若干个 Source/Timer/Observer。
     每次调用 RunLoop 的主函数时，只能指定其中一个 Mode，这个Mode被称作 CurrentMode。
     如果需要切换 Mode，只能退出 Loop，再重新指定一个 Mode 进入。
     这样做主要是为了分隔开不同组的 Source/Timer/Observer，让其互不影响。
 2、CFRunLoopSourceRef 主要是用来定义系统事件,和程序运行执行过程中的外部输入事件,如内核MachPort派发事件,CFSocket网络资源输入事件,外部touch事件,以及程序运行代码手动添加的事件。
     为了便于区分和管理. 苹果设定了两种source事件类型：Source0 和 Source1。
   （1）、Source0 它主要用于处理应用程序负责管理的事件,如UIEvent事件,CFSocket输入事件。它包含了一个回调指针,并不能主动的触发事件,而是依靠外部的事件来触发,比如用户点击了某个地方,或者网络接口的数据来了。需要先调用CFRunLoopSourceSingal(source),将这个source标记为待处理,然后手动调用CFRunLoopWakUp(runLoop)来唤醒runLoop后处理该事件
   （2）、Source1 主要是用来处理系统派发的事件,或者进程间通信(实际用不上,iOS为单进程,通常一次只能运行一个app).它是由系统内核来管理的,由MachPort进行驱动,如CFMatchPort(NSPort是对它的封装),CFMessagePort,每个进程被操作系统绑定了不同的PortId,通常情况下通过对该portId发送消息会触发source1事件。比如点击Xcode的暂停,起始底层调用了mach_msg_trap,使得程序进入暂停
 
 3、CFRunLoopTimerRef 是基于时间的触发器,(NSTimer是对它的上层封装).它包括了一个事件长度和一个回调指针。
    当加入到RunLoop的时候,RunLoop会注册对应的时间点,当时间到的时候,RunLoop会被自己注册的时间节点事件回调并唤醒,执行注册的时间节点数据。
    实际上我们app中有许多的时间都是依赖与NSTimer和CFRunLoopTimerRef,例如 CAAniamtion，CATransition，Push，Pop，CADisplayXXX，timerScheduleXXX,timerIntervalXXX,performDelay,PerformAfterXXX都是基于注册时间片回调事件来执行的。
    这里需要注意的是在执行动画操作的时候, RunLoop通常是在这一轮的循环中搜集完所有的动画执行事件之后再进行集中处理。

 4、CFRunLoopObserverRef 观察者的主要作用就是监视runLoop的生命周期和活动变化,
    简单来说吧就是 runLoop的创建－> 运行-> 挂起->唤醒->.... >死亡。
    runLoop在每次运行循环中把自己的状态变化通过注册回调指针告诉对应的observe,这样它的每一次状态变化时,observe都能通过回调指针获取它对应的状态,进行相关的处理

     typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
         kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop   objc_autoreleasePoolPush() 创建自动释放池
         kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
         kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
         kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
         kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
         kCFRunLoopExit          = (1UL << 7), // 即将退出Loop  _objc_autoreleasePoolPop() 来释放自动释放池
     };
 5、上面的 Source/Timer/Observer 被统称为 mode item，一个 item 可以被同时加入多个 mode。
    但一个 item 被重复加入同一个 mode 时是不会有效果的。
    如果一个 mode 中一个 item 都没有，则 RunLoop 会直接退出，不进入循环。
 
 四、RunLoop 的 Mode
 CFRunLoopMode 和 CFRunLoop 的结构大致如下：
     struct __CFRunLoopMode {
         CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"
         CFMutableSetRef _sources0;    // Set
         CFMutableSetRef _sources1;    // Set
         CFMutableArrayRef _observers; // Array
         CFMutableArrayRef _timers;    // Array
         ...
     };
 
     struct __CFRunLoop {
         CFMutableSetRef _commonModes;     // Set
         CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
         CFRunLoopModeRef _currentMode;    // Current Runloop Mode
         CFMutableSetRef _modes;           // Set
         ...
     };
 
   1、CommonModes：一个 Mode 可以将自己标记为”Common”属性（通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中）
      每当 RunLoop 的内容发生变化时，RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。
     例子：主线程的 RunLoop 里有两个预置的 Mode：kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。
          这两个 Mode 都已经被标记为”Common”属性。
          DefaultMode 是 App 平时所处的状态，TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。
          当你创建一个 Timer 并加到 DefaultMode 时，Timer 会得到重复回调，但此时滑动一个TableView时，RunLoop 会将 mode 切换为 TrackingRunLoopMode，这时 Timer 就不会被回调，并且也不会影响到滑动操作。
 
 五、RunLoop 的内部逻辑
 
    RunLoop 就是这样一个函数，其内部是一个 do-while 循环。当你调用 CFRunLoopRun() 时，线程就会一直停留在这个循环里；直到超时或被手动停止，该函数才会返回。
 六、RunLoop 的底层实现
    1、苹果官方将整个系统大致划分为上述4个层次：
    （1）、应用层：包括用户能接触到的图形应用，例如 Spotlight、Aqua、SpringBoard 等。
    （2）、应用框架层：即开发人员接触到的 Cocoa 等框架。
    （3）、核心框架层：包括各种核心框架、OpenGL 等内容。
    （4）、Darwin 即操作系统的核心，包括系统内核、驱动、Shell 等内容，
    2、Darwin 这个核心的架构：
    （1）、在硬件层上面的三个组成部分：Mach、BSD、IOKit (还包括一些上面没标注的内容)，共同组成了 XNU 内核。
          XNU 内核的内环被称作 Mach，其作为一个微内核，仅提供了诸如处理器调度、IPC (进程间通信)等非常少量的基础服务。
          BSD 层可以看作围绕 Mach 层的一个外环，其提供了诸如进程管理、文件系统和网络等功能。
          IOKit 层是为设备驱动提供了一个面向对象(C++)的一个框架。
    3、Mach 本身提供的 API 非常有限，而且苹果也不鼓励使用 Mach 的 API，但是这些API非常基础，如果没有这些API的话，其他任何工作都无法实施。
       在 Mach 中，所有的东西都是通过自己的对象实现的，进程、线程和虚拟内存都被称为”对象”。
      和其他架构不同， Mach 的对象间不能直接调用，只能通过消息传递的方式实现对象间的通信。
      ”消息”是 Mach 中最基础的概念，消息在两个端口 (port) 之间传递，这就是 Mach 的 IPC (进程间通信) 的核心。
 
    4、RunLoop 的核心就是一个 mach_msg() (见上面代码的第7步)，RunLoop 调用这个函数去接收消息，如果没有别人发送 port 消息过来，内核会将线程置于等待状态。例如你在模拟器里跑起一个 iOS 的 App，然后在 App 静止时点击暂停，你会看到主线程调用栈是停留在 mach_msg_trap() 这个地方。
 
 七、苹果是如何利用 RunLoop 实现自动释放池、延迟回调、触摸事件、屏幕刷新等功能的。

 
 
 
 
 */



/*
 基础：
 http://www.cocoachina.com/ios/20171226/21653.html  static关键字
 
 http://www.cocoachina.com/ios/20170224/18763.html
 
 
 RunLoop：让线程能随时处理事件但并不退出的一种机制
 
 RunLoop 如何使程序保活？
 UIApplicationMain()会创建主线程，主线程内部会主动开启一个RunLoop，而RunLoop本质上就是一个do-while循环，只要条件满足，就会不停的循环，进而程序一直保持运行的状态
 
 
 一直单纯这样循环是不是会影响性能？
 RunLoop在这机制的关键在于：如何管理事件/消息，如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒
 
 
 
 基于对CFRunLoopRef的源码分析，在 CoreFoundation 里面关于 RunLoop 有5个类:
 
 CFRunLoopRef //就是RunLoop，提供CFRunLoopGetMain()和CFRunLoopGetCurrent()
 CFRunLoopModeRef //RunLoop运行模式
 CFRunLoopSourceRef //RunLoop里面内容 -- 事件源，输入源
 CFRunLoopTimerRef //RunLoop里面内容 -- 定时器
 CFRunLoopObserverRef //RunLoop里面内容 -- 观察者
 
 一个RunLoop里面可以有多个mode，每个mode又可以多个source，observer，timer。**可是每次RunLoop只能指定一个mode运行，如果想要切换mode，就必须先退出RunLoop，然后重新指定mode运行，这样做的目的就是避免mode之间相互影响**
 
 
 创建RunLoop时，系统默认注册了五种mode：
 1. kCFRunLoopDefaultMode: 默认 mode，通常主线程在这个 mode 下运行
 2. UITrackingRunLoopMode: 追踪mode，保证Scrollview滑动顺畅不受其他 mode 影响
 3. UIInitializationRunLoopMode: 启动程序后的过渡mode，启动完成后就不再使用
 4: GSEventReceiveRunLoopMode: Graphic相关事件的mode，通常用不到
 5: kCFRunLoopCommonModes: 占位用的mode，作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用
 
 
 
 当scroll滑动时，同页面上的定时器为什么会暂停？
 
 创建一个timer时，系统默认把timer添加到kCFRunLoopDefaultMode模式中，但是当页面滚动的时候，RunLoop的Mode会自动切换成UITrackingRunLoopMode模式，因此timer失效，当停止滑动，RunLoop又会切换回NSDefaultRunLoopMode模式，因此timer又会重新启动了。
 kCFRunLoopCommonModes的存在就是来解决这个问题的，RunLoop运行时，会把kCFRunLoopCommonModes中的资源下发到每一个NSDefaultRunLoopMode和UITrackingRunLoopMode中，所以
 let timer = Timer.init(timeInterval: 1, repeats: true) { }
 RunLoop.main.add(timer, forMode: .commonModes)
 
 
 */

